﻿using System;
using System.Collections.Generic;
using System.Collections;

using OpenTK;

namespace MosCraft.Utilities
{
    public class AxisAlignedBB
    {
        private static ArrayList boundingBoxes = new ArrayList();
        private static int numBoundingBoxesInUse = 0;
        public Vector3d min;
        public Vector3d max;

        public static AxisAlignedBB getBoundingBox(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
        {
            return new AxisAlignedBB(minX, minY, minZ, maxX, maxY, maxZ);
        }

        public static void clearBoundingBoxes()
        {
            boundingBoxes.Clear();
            numBoundingBoxesInUse = 0;
        }

        public static void clearBoundingBoxPool()
        {
            numBoundingBoxesInUse = 0;
        }

        public static AxisAlignedBB getBoundingBoxFromPool(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
        {
            if (numBoundingBoxesInUse >= boundingBoxes.Count)
            {
                boundingBoxes.Add(getBoundingBox(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D));
            }
            return ((AxisAlignedBB)boundingBoxes[numBoundingBoxesInUse++]).setBounds(minX, minY, minZ, maxX, maxY, maxZ);
        }

        private AxisAlignedBB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
        {
            min.X = minX;
            min.Y = minY;
            min.Z = minZ;
            max.X = maxX;
            max.Y = maxY;
            max.Z = maxZ;
        }

        public AxisAlignedBB setBounds(double minX, double minY, double minZ, double maxX, double maxY, double maxZ)
        {
            min.X = minX;
            min.Y = minY;
            min.Z = minZ;
            max.X = maxX;
            max.Y = maxY;
            max.Z = maxZ;
            return this;
        }

        public AxisAlignedBB addCoord(Vector3d Coord)
        {
            return this.addCoord(Coord.X, Coord.Y, Coord.Z); 
        }

        public AxisAlignedBB addCoord(double x, double y, double z)
        {
            double d3 = min.X;
            double d4 = min.Y;
            double d5 = min.Z;
            double d6 = max.X;
            double d7 = max.Y;
            double d8 = max.Z;
            if (x < 0.0D)
                d3 += x;

            if (x > 0.0D)
                d6 += x;

            if (y < 0.0D)
                d4 += y;

            if (y > 0.0D)
                d7 += y;

            if (z < 0.0D)
                d5 += z;

            if (z > 0.0D)
                d8 += z;

            return getBoundingBoxFromPool(d3, d4, d5, d6, d7, d8);
        }

        public AxisAlignedBB expand(double x, double y, double z)
        {
            double d3 = min.X - x;
            double d4 = min.Y - y;
            double d5 = min.Z - z;
            double d6 = max.X + x;
            double d7 = max.Y + y;
            double d8 = max.Z + z;
            return getBoundingBoxFromPool(d3, d4, d5, d6, d7, d8);
        }

        public AxisAlignedBB getOffsetBoundingBox(double x, double y, double z)
        {
            return getBoundingBoxFromPool(min.X + x, min.Y + y, min.Z + z, max.X + x, max.Y + y, max.Z + z);
        }

        public double calculateXOffset(AxisAlignedBB axisalignedbb, double offset)
        {
            if (axisalignedbb.max.Y <= min.Y || axisalignedbb.min.Y >= max.Y)
            {
                return offset;
            }
            if (axisalignedbb.max.Z <= min.Z || axisalignedbb.min.Z >= max.Z)
            {
                return offset;
            }
            if (offset > 0.0D && axisalignedbb.max.X <= min.X)
            {
                double d1 = min.X - axisalignedbb.max.X;
                if (d1 < offset)
                {
                    offset = d1;
                }
            }
            if (offset < 0.0D && axisalignedbb.min.X >= max.X)
            {
                double d2 = max.X - axisalignedbb.min.X;
                if (d2 > offset)
                {
                    offset = d2;
                }
            }
            return offset;
        }

        public double calculateYOffset(AxisAlignedBB axisalignedbb, double offset)
        {
            if (axisalignedbb.max.X <= min.X || axisalignedbb.min.X >= max.X)
            {
                return offset;
            }
            if (axisalignedbb.max.Z <= min.Z || axisalignedbb.min.Z >= max.Z)
            {
                return offset;
            }
            if (offset > 0.0D && axisalignedbb.max.Y <= min.Y)
            {
                double d1 = min.Y - axisalignedbb.max.Y;
                if (d1 < offset)
                {
                    offset = d1;
                }
            }
            if (offset < 0.0D && axisalignedbb.min.Y >= max.Y)
            {
                double d2 = max.Y - axisalignedbb.min.Y;
                if (d2 > offset)
                {
                    offset = d2;
                }
            }
            return offset;
        }

        public double calculateZOffset(AxisAlignedBB axisalignedbb, double offset)
        {
            if (axisalignedbb.max.X <= min.X || axisalignedbb.min.X >= max.X)
            {
                return offset;
            }
            if (axisalignedbb.max.Y <= min.Y || axisalignedbb.min.Y >= max.Y)
            {
                return offset;
            }
            if (offset > 0.0D && axisalignedbb.max.Z <= min.Z)
            {
                double d1 = min.Z - axisalignedbb.max.Z;
                if (d1 < offset)
                {
                    offset = d1;
                }
            }
            if (offset < 0.0D && axisalignedbb.min.Z >= max.Z)
            {
                double d2 = max.Z - axisalignedbb.min.Z;
                if (d2 > offset)
                {
                    offset = d2;
                }
            }
            return offset;
        }

        public bool intersectsWith(AxisAlignedBB axisalignedbb)
        {
            if (axisalignedbb.max.X <= min.X || axisalignedbb.min.X >= max.X)
            {
                return false;
            }
            if (axisalignedbb.max.Y <= min.Y || axisalignedbb.min.Y >= max.Y)
            {
                return false;
            }
            return axisalignedbb.max.Z > min.Z && axisalignedbb.min.Z < max.Z;
        }

        public AxisAlignedBB offset(double x, double y, double z)
        {
            min.X += x;
            min.Y += y;
            min.Z += z;
            max.X += x;
            max.Y += y;
            max.Z += z;
            return this;
        }

        public bool isVecInside(Vector3d vector)
        {
            if (vector.X <= min.X || vector.X >= max.X)
            {
                return false;
            }
            if (vector.Y <= min.Y || vector.Y >= max.Y)
            {
                return false;
            }
            return vector.Z > min.Z && vector.Z < max.Z;
        }

        public double getAverageEdgeLength()
        {
            double d = max.X - min.X;
            double d1 = max.Y - min.Y;
            double d2 = max.Z - min.Z;
            return (d + d1 + d2) / 3D;
        }

        public AxisAlignedBB contract(double x, double y, double z)
        {
            double d3 = min.X + x;
            double d4 = min.Y + y;
            double d5 = min.Z + z;
            double d6 = max.X - x;
            double d7 = max.Y - y;
            double d8 = max.Z - z;
            return getBoundingBoxFromPool(d3, d4, d5, d6, d7, d8);
        }

        public AxisAlignedBB copy()
        {
            return getBoundingBoxFromPool(min.X, min.Y, min.Z, max.X, max.Y, max.Z);
        }

        private bool isVecInYZ(Vector3d vector)
        {
            if (vector == null)
            {
                return false;
            }
            else
            {
                return vector.Y >= min.Y && vector.Y <= max.Y && vector.Z >= min.Z && vector.Z <= max.Z;
            }
        }

        private bool isVecInXZ(Vector3d vector)
        {
            if (vector == null)
            {
                return false;
            }
            else
            {
                return vector.X >= min.X && vector.X <= max.X && vector.Z >= min.Z && vector.Z <= max.Z;
            }
        }

        private bool isVecInXY(Vector3d vector)
        {
            if (vector == null)
            {
                return false;
            }
            else
            {
                return vector.X >= min.X && vector.X <= max.X && vector.Y >= min.Y && vector.Y <= max.Y;
            }
        }

        public void setBB(AxisAlignedBB axisalignedbb)
        {
            min = axisalignedbb.min;
            max = axisalignedbb.max;
        }

        //public String toString()
        //{
        //    return (new StringBuilder()).append("box[").append(minX).append(", ").append(minY).append(", ").append(minZ).append(" -> ").append(maxX).append(", ").append(maxY).append(", ").append(maxZ).append("]").toString();
        //}

    }
}
